Telegram Group & Telegram Channel
🐍 Задача с подвохом: Декораторы и мутабельные ловушки

Условие:

Что выведет следующий код и почему?


def memoize(fn):
cache = {}
def wrapper(arg):
if arg in cache:
print("Из кэша")
return cache[arg]
else:
result = fn(arg)
cache[arg] = result
return result
return wrapper

@memoize
def add_to_list(val, lst=[]):
lst.append(val)
return lst

res1 = add_to_list(1)
res2 = add_to_list(2)
res3 = add_to_list(1)

print(res1)
print(res2)
print(res3)


Вопрос:
Что будет выведено? Где здесь двойной подвох?

🔍 Разбор:

На первый взгляд кажется, что:

1. add_to_list(1) вернёт [1]
2. add_to_list(2) вернёт [2]
3. add_to_list(1) снова вызовет функцию (или достанет из кэша)

Но тут два подвоха:

Подвох №1: изменяемый аргумент по умолчанию

Аргумент lst=[] создаётся один раз при определении функции. Все вызовы без передачи списка будут использовать один и тот же список.

Подвох №2: кэширование по ключу

Декоратор memoize кэширует результат по ключу arg. Но функция возвращает список, который изменяется при каждом вызове. Даже если кэш сработает, вы получите тот же объект списка, который менялся между вызовами!

🧮 Что реально произойдёт:

- `res1 = add_to_list(1)` → функция вызвана, список становится `[1]`
- `res2 = add_to_list(2)` → функция вызвана снова (новый аргумент), список становится `[1, 2]`
- `res3 = add_to_list(1)` → аргумент `1` есть в кэше, сработает ветка `print("Из кэша")` и вернётся **ссылку на тот же изменённый список**

🔢 **Вывод:**

```
[1, 2]
[1, 2]
Из кэша
[1, 2]
```

Все результаты указывают на один и тот же изменённый список.

💥 **Почему это важно:**

1️⃣ **Изменяемые аргументы по умолчанию** сохраняются между вызовами
2️⃣ **Кэширование мутабельных объектов** может привести к неожиданным результатам: при возврате списка вы возвращаете не "результат на момент вычисления", а ссылку на объект, который может измениться позже

🛡️ **Как исправить:**

1️⃣ Использовать `lst=None` и инициализировать внутри функции:
```python
def add_to_list(val, lst=None):
if lst is None:
lst = []
lst.append(val)
return lst
```

2️⃣ Если кэшировать мутабельные объекты, лучше возвращать **копию**:
```python
import copy
cache[arg] = copy.deepcopy(result)
```

**Вывод:**

Декораторы + мутабельные аргументы = ловушка даже для опытных разработчиков. Особенно, когда мутабельные объекты кэшируются и меняются за кулисами.


@pythonl



tg-me.com/pythonl/4798
Create:
Last Update:

🐍 Задача с подвохом: Декораторы и мутабельные ловушки

Условие:

Что выведет следующий код и почему?


def memoize(fn):
cache = {}
def wrapper(arg):
if arg in cache:
print("Из кэша")
return cache[arg]
else:
result = fn(arg)
cache[arg] = result
return result
return wrapper

@memoize
def add_to_list(val, lst=[]):
lst.append(val)
return lst

res1 = add_to_list(1)
res2 = add_to_list(2)
res3 = add_to_list(1)

print(res1)
print(res2)
print(res3)


Вопрос:
Что будет выведено? Где здесь двойной подвох?

🔍 Разбор:

На первый взгляд кажется, что:

1. add_to_list(1) вернёт [1]
2. add_to_list(2) вернёт [2]
3. add_to_list(1) снова вызовет функцию (или достанет из кэша)

Но тут два подвоха:

Подвох №1: изменяемый аргумент по умолчанию

Аргумент lst=[] создаётся один раз при определении функции. Все вызовы без передачи списка будут использовать один и тот же список.

Подвох №2: кэширование по ключу

Декоратор memoize кэширует результат по ключу arg. Но функция возвращает список, который изменяется при каждом вызове. Даже если кэш сработает, вы получите тот же объект списка, который менялся между вызовами!

🧮 Что реально произойдёт:

- `res1 = add_to_list(1)` → функция вызвана, список становится `[1]`
- `res2 = add_to_list(2)` → функция вызвана снова (новый аргумент), список становится `[1, 2]`
- `res3 = add_to_list(1)` → аргумент `1` есть в кэше, сработает ветка `print("Из кэша")` и вернётся **ссылку на тот же изменённый список**

🔢 **Вывод:**

```
[1, 2]
[1, 2]
Из кэша
[1, 2]
```

Все результаты указывают на один и тот же изменённый список.

💥 **Почему это важно:**

1️⃣ **Изменяемые аргументы по умолчанию** сохраняются между вызовами
2️⃣ **Кэширование мутабельных объектов** может привести к неожиданным результатам: при возврате списка вы возвращаете не "результат на момент вычисления", а ссылку на объект, который может измениться позже

🛡️ **Как исправить:**

1️⃣ Использовать `lst=None` и инициализировать внутри функции:
```python
def add_to_list(val, lst=None):
if lst is None:
lst = []
lst.append(val)
return lst
```

2️⃣ Если кэшировать мутабельные объекты, лучше возвращать **копию**:
```python
import copy
cache[arg] = copy.deepcopy(result)
```

**Вывод:**

Декораторы + мутабельные аргументы = ловушка даже для опытных разработчиков. Особенно, когда мутабельные объекты кэшируются и меняются за кулисами.


@pythonl

BY Python/ django


Warning: Undefined variable $i in /var/www/tg-me/post.php on line 283

Share with your friend now:
tg-me.com/pythonl/4798

View MORE
Open in Telegram


Python django Telegram | DID YOU KNOW?

Date: |

Telegram announces Anonymous Admins

The cloud-based messaging platform is also adding Anonymous Group Admins feature. As per Telegram, this feature is being introduced for safer protests. As per the Telegram blog post, users can “Toggle Remain Anonymous in Admin rights to enable Batman mode. The anonymized admin will be hidden in the list of group members, and their messages in the chat will be signed with the group name, similar to channel posts.”

Python django from ca


Telegram Python/ django
FROM USA